#lang racket
;; A bunch of code in this file was copied from the irc package
;; (c) Jonathan Schuster
(provide (all-defined-out))

;; :prefix command params crlf
(struct irc-message (prefix command params) #:transparent)
(struct irc-connection (in out [nick #:mutable] user [ws-conn #:mutable]))

(define/contract (send-to-client conn message)
  (-> irc-connection? irc-message? void?)
  ;; TODO: flush?
  (match message
    [(irc-message prefix command params)
     (if prefix
         (fprintf (irc-connection-out conn)
                  ":~a ~a ~a\r\n"
                  prefix
                  command
                  (string-join params))
         (fprintf (irc-connection-out conn)
                  "~a ~a\r\n"
                  command
                  (string-join params)))]))

;; blocking read
(define/contract (read-from-client conn)
  (-> irc-connection? (or/c irc-message? false?))
  (define in (irc-connection-in conn))
  (if (port-closed? in)
      #f
      (let ([line (read-line in)])
        (if (string? line)
            (parse-message line)
            #f))))


;; Given the string of an IRC message, returns an irc-message that has been parsed as far as possible,
;; or #f if the input was unparsable
(define (parse-message message)
  (define parts (string-split (string-trim message) " " #:trim? #f))
  (define prefix (if (and (pair? parts)
                          (string-starts-with? (list-ref parts 0) ":"))
                     (substring (list-ref parts 0) 1)
                     #f))
  (cond [(> (length parts) (if prefix 1 0))
         (define command (list-ref parts (if prefix 1 0)))
         (define param-parts (list-tail parts (if prefix 2 1)))
         (irc-message prefix command (parse-params param-parts))]
        [else #f]))

;; Given the list of param parts, return the list of params
(define (parse-params parts)
  (define first-tail-part (find-first-tail-part parts))
  (cond [first-tail-part
         (define tail-with-colon (string-join (list-tail parts first-tail-part)))
         (define tail-param (if (string-starts-with? tail-with-colon ":")
                                (substring tail-with-colon 1)
                                tail-with-colon))
         (append (take parts first-tail-part)
                 (list tail-param))]
        [else parts]))

;; Return the index of the first part that starts the tail parameters; of #f if no tail exists
(define (find-first-tail-part param-parts)
  (define first-colon-index (memf/index (lambda (v) (string-starts-with? v ":"))
                                        param-parts))
  (cond [(or first-colon-index (> (length param-parts) 14))
         (min 14 (if first-colon-index first-colon-index 14))]
        [else #f]))

;; Like memf, but returns the index of the first item to satisfy proc instead of
;; the list starting at that item.
(define (memf/index proc lst)
  (define memf-result (memf proc lst))
  (cond [memf-result (- (length lst) (length memf-result))]
        [else #f]))

(define (string-starts-with? s1 s2)
  (define s1-prefix (if (= 0 (string-length s1)) "" (substring s1 0 (string-length s2))))
  (equal? s1-prefix s2))


(define RPL_TOPIC 332)
(define RPL_NAMEREPLY 353)
(define RPL_ENDOFNAMES 366)
(define RPL_CHANNELMODEIS 324)
(define RPL_WHOREPLY 352)
(define RPL_ENDOFWHO 315)
